package pl.accuratus.exmore.rebind;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.accuratus.exmore.client.spi.AbstractExtensionPoint;
import pl.accuratus.exmore.client.spi.AbstractExtensionRegistry;
import pl.accuratus.exmore.client.spi.AnnotationFactory;
import pl.accuratus.exmore.client.spi.AnnotationParameterHolder;
import pl.accuratus.exmore.client.spi.DefaultAnnotation;
import pl.accuratus.exmore.client.spi.ExtensionMetaData;
import pl.accuratus.exmore.client.spi.MetaData;
import pl.accuratus.exmore.rebind.ExtensionRegistryGenerator.SimpleArtefactBuilder.AccessType;
import pl.accuratus.exmore.rebind.ExtensionRegistryGenerator.SimpleArtefactBuilder.ArtefactType;
import pl.accuratus.exmore.rebind.ExtensionRegistryGenerator.SimpleArtefactBuilder.SimpleMethodBuilder;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class ExtensionRegistryGenerator extends Generator {

	private TypeOracle typeOracle;
	private int extPointCounter = 0;
	String implName = "";
	final private Map<Class<? extends Annotation>, String> extPoints = new HashMap<Class<? extends Annotation>, String>();
	final private Map<Class<? extends Annotation>, String> generatedClass = new HashMap<Class<? extends Annotation>, String>();
	final private Map<Class<?>, Class<?>> primitivsToObject = new HashMap<Class<?>, Class<?>>();
	final private StringBuilder preConstructor = new StringBuilder();
	final private StringBuilder initBlock = new StringBuilder();

	protected String genericClassName(JClassType parameter) {
		String result = parameter.getQualifiedSourceName();
		if (parameter.isParameterized() != null) {
			result = parameter.isParameterized().getParameterizedQualifiedSourceName();
		}

		return result;
	}

	protected Collection<JField> getFields(JClassType requestedType) {
		Collection<JField> result = new LinkedList<JField>();
		if (requestedType.getSuperclass() != null) {
			result.addAll(getFields(requestedType.getSuperclass()));
			for (JField field : requestedType.getFields()) {
				if (field.isProtected() || field.isPublic())
					result.add(field);
			}
		}
		return result;
	}

	@Override
	public String generate(TreeLogger logger, GeneratorContext ctx, String typeName) throws UnableToCompleteException {

		primitivsToObject.put(int.class, Integer.class);
		primitivsToObject.put(float.class, Float.class);
		primitivsToObject.put(double.class, Double.class);
		primitivsToObject.put(long.class, Long.class);
		primitivsToObject.put(short.class, Short.class);
		primitivsToObject.put(byte.class, Byte.class);
		primitivsToObject.put(char.class, Character.class);
		primitivsToObject.put(boolean.class, Boolean.class);
		primitivsToObject.put(String.class, String.class);

		typeOracle = ctx.getTypeOracle();

		JClassType extensionRegistryType = typeOracle.findType(AbstractExtensionRegistry.class.getCanonicalName());

		String packageName = extensionRegistryType.getPackage().getName();
		String simpleName = "ExtensionRegistry_Impl";

		implName = packageName + "." + simpleName;

		JClassType findType = typeOracle.findType(implName);
		if (findType != null)
			return implName;

		PrintWriter printWriter = ctx.tryCreate(logger, packageName, simpleName);
		if (printWriter == null)
			return implName;

		ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, simpleName);
		composerFactory.setSuperclass(extensionRegistryType.getQualifiedSourceName());

		composerFactory.addImport(HashSet.class.getCanonicalName());
		composerFactory.addImport(Set.class.getCanonicalName());
		composerFactory.addImport(GWT.class.getCanonicalName());
		composerFactory.addImport(AsyncCallback.class.getCanonicalName());
		composerFactory.addImport(RunAsyncCallback.class.getCanonicalName());
		composerFactory.addImport(AnnotationParameterHolder.class.getCanonicalName());

		SourceWriter srcWriter = composerFactory.createSourceWriter(ctx, printWriter);

		// fields
		StringBuilder s = new StringBuilder();
		preConstructor.append("final " + AnnotationParameterHolder.class.getSimpleName() + " paramsHolder = new "
				+ AnnotationParameterHolder.class.getSimpleName() + "();");

		// constructor
		SimpleMethodBuilder mainConstructor = new SimpleArtefactBuilder.SimpleMethodBuilder(simpleName)
				.setAs(AccessType.PUBLIC);

		scanForAnnotationsMarkedByMetaData(logger, s);
		mainConstructor.addBodyLine(initBlock.toString());
		mainConstructor.addBodyLine(s.toString());
		for (Class<? extends Annotation> key : extPoints.keySet()) {
			String annotationName = key.getCanonicalName();
			String className = extPoints.get(key);
			mainConstructor.addBodyLine("registerExtensionPoint( " + annotationName + ".class, new " + className
					+ "() );");
		}
		

		String result = preConstructor.append(mainConstructor.buildAsConstructor()).toString();

		logger.log(TreeLogger.INFO, result);
		srcWriter.print(result);
		srcWriter.commit(logger);
		return implName;
	}

	private String createAnnotationInstance(Class<? extends Annotation> annotation, JClassType singleExtensionClass) {
		String className = annotation.getSimpleName() + "_Impl";
		String superClass = DefaultAnnotation.class.getCanonicalName() + "<" + annotation.getCanonicalName() + ">";

		SimpleArtefactBuilder builder = new SimpleArtefactBuilder(className)
				.create(ArtefactType.CLASS)
				.extending(superClass)
				.implementing(annotation.getCanonicalName());

		Annotation correctExtensionAnnotation = singleExtensionClass.getAnnotation(annotation);
		if (correctExtensionAnnotation != null) {
			for (Method method : correctExtensionAnnotation.getClass().getDeclaredMethods()) {
				if ("equals".equals(method.getName()) || "hashCode".equals(method.getName())
						|| "toString".equals(method.getName())) {
					continue;
				}
				Object invokeResult = null;
				try {
					invokeResult = method.invoke(correctExtensionAnnotation);

					if (invokeResult instanceof Class<?>) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsClass(s, method, (Class<?>) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Integer) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsInt(s, method, (Integer) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Boolean) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsBoolean(s, method, (Boolean) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Long) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsLong(s, method, (Long) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Double) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsDouble(s, method, (Double) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Float) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsFloat(s, method, (Float) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Short) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsShort(s, method, (Short) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Byte) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsByte(s, method, (Byte) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Character) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsChar(s, method, (Character) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof String) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsString(s, method, (String) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Enum) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsEnum(s, method, (Enum<?>) invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult.getClass().isArray()) {
						StringBuilder s = new StringBuilder();
						generateMethodWhichReturnsArray(s, method, invokeResult);
						builder.addMethod(s.toString());
					} else if (invokeResult instanceof Annotation) {
						// Annotation a = (Annotation) invokeResult;
						// s.append("\n\n\t@Override\tpublic "+a.getClass().getCanonicalName()+" "
						// + method.getName()+ "(){\n\t\treturn " + a
						// +";\n\t}");
					} else
						throw new Exception();
				} catch (Exception e) {
					if (invokeResult != null) {
						// logger.log(TreeLogger.WARN, "Class: "
						// + invokeResult.getClass().getCanonicalName()
						// + " is not supported type for extension parameter");
					}
				}
			}
		}
		return builder.build();
	}

	private void generateMethodWhichReturnsArray(StringBuilder s, Method method, Object invokeResult) {
		Class<?> type = invokeResult.getClass().getComponentType();
		int arrlength = Array.getLength(invokeResult);

		String array = "";
		for (int i = 0; i < arrlength; ++i) {
			array += (type.equals(String.class) ? "\"" : type.equals(char.class) ? "\'" : "")
					+ Array.get(invokeResult, i)
					+ (type.equals(String.class) ? "\"" : type.equals(char.class) ? "\'" : "");

			array += (type.equals(float.class) ? "f" : type.equals(double.class) ? "d"
					: type.equals(Class.class) ? ".class" : "");

			if (i + 1 < arrlength)
				array += ", ";
		}
		if (type.equals(Class.class))
			array = array.replaceAll("class ", "");

		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(type.getCanonicalName() + (type.equals(Class.class) ? "<?>" : "") + "[] ")
						.addBodyLine(
								"return new " + type.getCanonicalName() + (type.equals(Class.class) ? "<?>" : "")
										+ "[]{" + array + "};")
						.build()
				);

	}

	private void generateMethodWhichReturnsEnum(StringBuilder s, Method method, Enum<?> e) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(e.getClass())
						.addBodyLine("return " + e.getClass().getCanonicalName() + "." + e.name() + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsString(StringBuilder s, Method method, String str) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(String.class)
						.addBodyLine("return \"" + str + "\";")
						.build()
				);
	}

	private void generateMethodWhichReturnsChar(StringBuilder s, Method method, char c) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(char.class)
						.addBodyLine("return '" + c + "';")
						.build()
				);
	}

	private void generateMethodWhichReturnsByte(StringBuilder s, Method method, byte b) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(byte.class)
						.addBodyLine("return " + b + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsShort(StringBuilder s, Method method, short sh) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(short.class)
						.addBodyLine("return " + sh + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsDouble(StringBuilder s, Method method, double d) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(double.class)
						.addBodyLine("return " + d + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsFloat(StringBuilder s, Method method, float f) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(float.class)
						.addBodyLine("return " + f + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsLong(StringBuilder s, Method method, long l) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(long.class)
						.addBodyLine("return " + l + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsBoolean(StringBuilder s, Method method, boolean b) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(boolean.class)
						.addBodyLine("return " + b + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsInt(StringBuilder s, Method method, int i) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType(int.class)
						.addBodyLine("return " + i + ";")
						.build()
				);
	}

	private void generateMethodWhichReturnsClass(StringBuilder s, Method method, Class<?> clazz) {
		s.append(
				new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
						.addAnnotation(Override.class)
						.setAs(AccessType.PUBLIC)
						.setReturnType("Class<" + clazz.getCanonicalName() + ">")
						.addBodyLine("return " + clazz.getCanonicalName() + ".class;")
						.build()
				);
	}

	void scanForAnnotationsMarkedByMetaData(TreeLogger logger, StringBuilder s) {
		JClassType annotationSuperClass = typeOracle.findType(Annotation.class.getCanonicalName());
		JClassType[] subtypes = annotationSuperClass.getSubtypes();
		logger.log(Type.INFO, "################### Annotations : " + subtypes.length + " ######################");
		StringBuilder extPointBuilder = new StringBuilder();
		StringBuilder annotationBuilder = new StringBuilder();
		for (JClassType singleAnnotation : subtypes) {
			MetaData metaData = singleAnnotation.getAnnotation(MetaData.class);
			if (metaData != null) {
				try {
					@SuppressWarnings("unchecked")
					Class<? extends Annotation> annotation = (Class<? extends Annotation>) Class
							.forName(singleAnnotation.getQualifiedBinaryName());
					generateAnnotationFactory(annotation, annotationBuilder);
					generateExtensionPointClass(logger, extPointBuilder, annotation, metaData.extensionsClass());
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
				}
			}
		}
		preConstructor.append(annotationBuilder.toString());
		s.append(extPointBuilder.toString());
	}

	private void generateAnnotationFactory(Class<? extends Annotation> annotation, StringBuilder annotationBuilder) {
		String superClass = AnnotationFactory.class.getSimpleName();
		String className = annotation.getSimpleName() + superClass + "_Impl";
		String annotationClass = annotation.getCanonicalName();
		SimpleArtefactBuilder factory = new SimpleArtefactBuilder(className)
				.create(ArtefactType.CLASS)
				.setAs(AccessType.PUBLIC)
				.setAsStatic()
				.extending(superClass + "<" + annotationClass + ">")
				.addMethod(
						new SimpleArtefactBuilder.SimpleMethodBuilder("build")
								.addAnnotation(Override.class)
								.setAs(AccessType.PUBLIC)
								.setReturnType(annotationClass)
								.addBodyLine(createAnnotationInstance(annotation))
								.addBodyLine("return new " + annotation.getSimpleName() + "_Impl();")
								.build()
				);

		generatedClass.put(annotation, className);
		annotationBuilder.append(factory.build());
	}

	private String createAnnotationInstance(Class<? extends Annotation> annotation) {
		String className = annotation.getSimpleName() + "_Impl";
		String superClass = DefaultAnnotation.class.getCanonicalName() + "<" + annotation.getCanonicalName() + ">";

		SimpleArtefactBuilder builder = new SimpleArtefactBuilder(className)
				.create(ArtefactType.CLASS)
				.extending(superClass)
				.implementing(annotation.getCanonicalName());

		for (Method method : annotation.getMethods()) {
			if ("equals".equals(method.getName()) || "hashCode".equals(method.getName())
						|| "toString".equals(method.getName())) {
				continue;
			}

			String returnCast = primitivsToObject.containsKey(method.getReturnType()) ?
						primitivsToObject.get(method.getReturnType()).getSimpleName() :
						method.getReturnType().getCanonicalName();
			builder.addMethod(
						new SimpleArtefactBuilder.SimpleMethodBuilder(method.getName())
								.addAnnotation(Override.class)
								.setAs(AccessType.PUBLIC)
								.setReturnType(method.getReturnType())
								.addBodyLine(
										"return (" + returnCast + ") parameters.get(\"" + method.getName() + "\");")
								.build());
		}

		return builder.build();
	}

	public void generateExtensionPointClass(TreeLogger logger, StringBuilder s,
			Class<? extends Annotation> supportedAnnotation, Class<?> interfaceClass) {
		JClassType extensionClassRoot = typeOracle.findType(interfaceClass.getCanonicalName());
		if (extensionClassRoot == null)
			return;

		JClassType[] classesAnnotatedBySupportedAnnotation = extensionClassRoot.getSubtypes();

		String className = "ExtensionPoint_Impl" + (extPointCounter++);
		String superClassName = AbstractExtensionPoint.class.getCanonicalName();
		String annotationName = supportedAnnotation.getCanonicalName();
		String superClass = superClassName + "<" + annotationName + ">";

		SimpleArtefactBuilder extPoint = new SimpleArtefactBuilder(className)
				.create(ArtefactType.CLASS)
				.extending(superClass);

		SimpleMethodBuilder constructor = new SimpleArtefactBuilder.SimpleMethodBuilder(className)
				.setAs(AccessType.PUBLIC);

		if (classesAnnotatedBySupportedAnnotation != null) {

			String mdClassName = ExtensionMetaData.class.getSimpleName();
			String mdAnnotationName = supportedAnnotation.getCanonicalName();
			constructor.addBodyLine(mdClassName + "<" + mdAnnotationName + "> md = null;");
			for (JClassType extensionImpl : classesAnnotatedBySupportedAnnotation) {
				StringBuilder constrBody = new StringBuilder();
				generateExtensionMetaDataClass(constrBody, supportedAnnotation, extensionImpl);
				constructor.addBodyLine("md = " + constrBody.toString() + ";");
				constructor.addBodyLine("registerExtension( md );");
			}
		}

		extPoint.addMethod(constructor.buildAsConstructor());
		extPoints.put(supportedAnnotation, className);

		s.append(extPoint.build());

	}

	public void generateExtensionMetaDataClass(StringBuilder s, Class<? extends Annotation> supportedAnnotation,
			JClassType extensionImplClass) {
		String className = ExtensionMetaData.class.getSimpleName();
		String annotationName = supportedAnnotation.getCanonicalName();
		String extensionImplClassName = extensionImplClass.getQualifiedBinaryName();
		Boolean isAsyncAccessNeeded = true;

		try {
			registerAnnotationsParams(Class.forName(extensionImplClassName), extensionImplClass.getAnnotations());
		} catch (ClassNotFoundException e) {

		}

		String params = "paramsHolder.getParameters("+extensionImplClassName+".class, "+annotationName+".class)";
		SimpleArtefactBuilder md = new SimpleArtefactBuilder(className + "<" + annotationName + ">")
				.addParameter("new ExtensionRegistry_Impl." + generatedClass.get(supportedAnnotation) + "().init("+params+").build()");

		if (isAsyncAccessNeeded) {
			md.addMethod(
					new SimpleArtefactBuilder.SimpleMethodBuilder("create")
							.addAnnotation(Override.class)
							.setAs(AccessType.PUBLIC)
							.setReturnType("<C> C")
							.addBodyLine("return null;")
							.build()
					);

			SimpleArtefactBuilder runAsyncCallback = new SimpleArtefactBuilder("RunAsyncCallback")
					.addMethod(
							new SimpleArtefactBuilder.SimpleMethodBuilder("onSuccess")
									.addAnnotation(Override.class)
									.setAs(AccessType.PUBLIC)
									.setReturnType(void.class)
									.addBodyLine(
											"callback.onSuccess( (C) new "
													+ extensionImplClassName + "() );")
									.build()
					)
					.addMethod(
							new SimpleArtefactBuilder.SimpleMethodBuilder("onFailure")
									.addAnnotation(Override.class)
									.setAs(AccessType.PUBLIC)
									.setReturnType(void.class)
									.addParameter(Throwable.class, "reason")
									.addBodyLine(
											"callback.onFailure(reason);")
									.build()
					);

			md.addMethod(
					new SimpleArtefactBuilder.SimpleMethodBuilder("createAsync")
							.addAnnotation(Override.class)
							.setAs(AccessType.PUBLIC)
							.setReturnType("<C> void")
							.addParameter("final AsyncCallback<C>", "callback")
							.addBodyLine("GWT.runAsync(")
							.addBodyLine(runAsyncCallback.buildAsNew())
							.addBodyLine(");")
							.build()
					);

		} else {
			md.addMethod(
					new SimpleArtefactBuilder.SimpleMethodBuilder("create")
							.addAnnotation(Override.class)
							.setAs(AccessType.PUBLIC)
							.setReturnType("<C> C")
							.addBodyLine("return (C) new " + extensionImplClassName + "();")
							.build()
					);
			md.addMethod(
					new SimpleArtefactBuilder.SimpleMethodBuilder("createAsync")
							.addAnnotation(Override.class)
							.setAs(AccessType.PUBLIC)
							.setReturnType("<C> void")
							.addParameter("final AsyncCallback<C>", "callback")
							.addBodyLine("callback.onFailure(new " + RuntimeException.class.getCanonicalName() + ");")
							.build()
					);

		}

		s.append(md.buildAsNew());

	}

	private void registerAnnotationParameters(Class<?> annotatedClass, Annotation annotation) {
		StringBuilder sb = new StringBuilder();

		for (Method method : annotation.annotationType().getMethods()) {
			if (!method.getName().equalsIgnoreCase("toString") &&
					!method.getName().equalsIgnoreCase("equals") &&
					!method.getName().equalsIgnoreCase("hashCode")) {

				sb.append("\nparamsHolder.addParameter(");
				sb.append(annotatedClass.getCanonicalName() + ".class, ");
				sb.append(annotation.annotationType().getCanonicalName() + ".class, ");
				sb.append("\""+method.getName() + "\", ");
				try {
					Object invoke = method.invoke(annotation);
					sb.append(returnAsString(invoke));
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				} catch (Exception e) {
					sb.append("null");
				}
				sb.append(");");
			}
		}
		initBlock.append(sb.toString());
	}

	private void registerAnnotationsParams(Class<?> annotatedClass, Annotation[] annotations) {
		if (annotatedClass != null && annotations != null) {
			for (Annotation a : annotations) {
				registerAnnotationParameters(annotatedClass, a);
			}
		}
	}

	public static class SimpleArtefactBuilder {

		public static enum AccessType {
			DEFAULT, PROTECTED, PRIVATE, PUBLIC
		}

		public static enum ArtefactType {
			CLASS, INTERFACE, ENUM
		}

		public static class SimpleMethodBuilder {
			private String methodName;
			private AccessType accessType = AccessType.DEFAULT;
			private boolean isStatic = false;
			private boolean isAbstract = false;
			private String returnType = void.class.getCanonicalName();
			private List<String> body = new ArrayList<String>();
			private List<Class<? extends Annotation>> annotations = new ArrayList<Class<? extends Annotation>>();
			private Map<String, String> parameters = new HashMap<String, String>();
			public boolean skipReturnType = false;

			public SimpleMethodBuilder(String methodName) {
				this.methodName = methodName;
			}

			public SimpleMethodBuilder setAs(AccessType accessType) {
				this.accessType = accessType;
				return this;
			}

			public SimpleMethodBuilder setAsStatic() {
				this.isStatic = true;
				return this;
			}

			public SimpleMethodBuilder setAsAbstract() {
				this.isAbstract = true;
				return this;
			}

			public SimpleMethodBuilder setReturnType(Class<?> returnType) {
				this.returnType = returnType.getCanonicalName();
				return this;
			}

			public SimpleMethodBuilder setReturnType(String genericType) {
				this.returnType = genericType;
				return this;
			}

			public SimpleMethodBuilder addBodyLine(String bodyLine) {
				body.add(bodyLine);
				return this;
			}

			public SimpleMethodBuilder addAnnotation(Class<? extends Annotation> annotation) {
				if (!annotations.contains(annotation))
					annotations.add(annotation);
				return this;
			}

			public SimpleMethodBuilder addParameter(Class<?> type, String name) {
				parameters.put(name, type.getCanonicalName());
				return this;
			}

			public SimpleMethodBuilder addParameter(String genericType, String name) {
				parameters.put(name, genericType);
				return this;
			}

			public String build() {
				StringBuilder s = new StringBuilder();
				s.append("\n");
				for (Class<? extends Annotation> a : annotations) {
					s.append("@" + a.getCanonicalName() + "\n");
				}
				s.append(accessType != AccessType.DEFAULT ? accessType.name().toLowerCase() : "");
				s.append(isStatic ? " static" : "");
				s.append(isAbstract ? " abstract" : "");
				if (!skipReturnType)
					s.append(" " + returnType);
				s.append(" " + methodName);
				s.append("(");
				int i = 0;
				for (String pName : parameters.keySet()) {
					if (i > 0)
						s.append(",");
					s.append(" " + parameters.get(pName) + " " + pName);
					i++;
				}
				s.append(") {");
				for (String bodyLine : body) {
					String[] lines = bodyLine.split("\\n");
					for (String line : lines) {
						s.append("\n\t" + line);
					}
				}
				s.append("\n}");
				return s.toString();
			}

			public String buildAsConstructor() {
				skipReturnType = true;
				return build();
			}
		}

		private String className;
		private ArtefactType artefactType = ArtefactType.CLASS;
		private AccessType accessType = AccessType.DEFAULT;
		private boolean isStatic = false;
		private boolean isAbstract = false;
		private String superClass;
		private List<String> interfaces = new ArrayList<String>();
		private List<String> methods = new ArrayList<String>();
		private List<String> parameters = new ArrayList<String>();
		private boolean isNewInstance = false;

		public SimpleArtefactBuilder(String className) {
			this.className = className;
		}

		public SimpleArtefactBuilder create(ArtefactType artefactType) {
			this.artefactType = artefactType;
			return this;
		}

		public SimpleArtefactBuilder setAs(AccessType accessType) {
			this.accessType = accessType;
			return this;
		}

		public SimpleArtefactBuilder setAsStatic() {
			this.isStatic = true;
			return this;
		}

		public SimpleArtefactBuilder setAsAbstract() {
			this.isAbstract = true;
			return this;
		}

		public SimpleArtefactBuilder extending(String superClass) {
			this.superClass = superClass;
			return this;
		}

		public SimpleArtefactBuilder implementing(String interfaceName) {
			if (!interfaces.contains(interfaceName))
				interfaces.add(interfaceName);
			return this;
		}

		public SimpleArtefactBuilder addMethod(String method) {
			methods.add(method);
			return this;
		}

		public String build() {
			StringBuilder s = new StringBuilder();
			s.append("\n");
			if (!isNewInstance) {
				s.append(accessType != AccessType.DEFAULT ? accessType.name().toLowerCase() : "");
				s.append(isStatic ? " static" : "");
				s.append(artefactType == ArtefactType.CLASS && isAbstract ? " abstract" : "");
				s.append(" " + artefactType.name().toLowerCase());
			} else {
				s.append(" new");
			}
			s.append(" " + className);

			s.append(artefactType != ArtefactType.ENUM && superClass != null ? " extends " + superClass : "");
			s.append(artefactType == ArtefactType.CLASS && !interfaces.isEmpty() ? " implements" : "");
			for (int i = 0; artefactType == ArtefactType.CLASS && i < interfaces.size(); i++) {
				if (i > 0)
					s.append(",");
				s.append(" " + interfaces.get(i));
			}
			if (isNewInstance) {
				s.append("(");
				int i = 0;
				for (String p : parameters) {
					if (i > 0)
						s.append(",");
					s.append(p);
					i++;
				}
				s.append(")");
			}
			s.append(" {");
			for (String m : methods) {
				String[] lines = m.split("\\n");
				for (String line : lines) {
					s.append("\n\t" + line);
				}
			}
			s.append("\n}");
			return s.toString();
		}

		public String buildAsNew() {
			isNewInstance = true;
			return build();
		}

		public SimpleArtefactBuilder addParameter(String parameter) {
			parameters.add(parameter);
			return this;
		}
	}

	public static void main(String[] args) {
		System.out.println(new SimpleArtefactBuilder("ExtensionRegistryGenerator")
				.create(ArtefactType.CLASS)
				.setAs(AccessType.PUBLIC)
				.extending("Generator")
				.addMethod(
						new SimpleArtefactBuilder.SimpleMethodBuilder("ExtensionRegistryGenerator")
								.setAs(AccessType.PUBLIC)
								.addParameter(String.class, "name")
								.addParameter("C", "name2")
								.buildAsConstructor()
				)
				.addMethod(
						new SimpleArtefactBuilder.SimpleMethodBuilder("test")
								.setAs(AccessType.PUBLIC).setAsStatic()
								.setAsAbstract()
								.setReturnType(void.class)
								.addBodyLine("//comment")
								.addAnnotation(Override.class)
								.build())
				.addMethod(
						new SimpleArtefactBuilder.SimpleMethodBuilder("test2")
								.setAs(AccessType.PUBLIC)
								.setReturnType("<C> C")
								.addBodyLine("return (C) \"Hello World!\";")
								.build())
				.addMethod(new SimpleArtefactBuilder.SimpleMethodBuilder("test3")
						.build())
						.addParameter("new String()")
				.buildAsNew());

	}

	private String returnAsString(Object o) {
		StringBuilder result = new StringBuilder();

		if (o instanceof Class<?>) {
			result.append(((Class<?>) o).getCanonicalName() + ".class");
		} else if (o instanceof Integer) {
			result.append(((Integer) o).intValue());
		} else if (o instanceof Boolean) {
			result.append(((Boolean) o).booleanValue());
		} else if (o instanceof Long) {
			result.append(((Long) o).longValue());
		} else if (o instanceof Double) {
			result.append(((Double) o).doubleValue());
		} else if (o instanceof Float) {
			result.append(((Float) o).floatValue());
		} else if (o instanceof Short) {
			result.append(((Short) o).shortValue());
		} else if (o instanceof Byte) {
			result.append(((Byte) o).byteValue());
		} else if (o instanceof Character) {
			result.append("'"+((Character) o).charValue()+"'");
		} else if (o instanceof String) {
			result.append("\""+(String) o+"\"");
		} else if (o instanceof Enum) {
			Enum<?> e = (Enum<?>) o;
			result.append(e.getClass().getCanonicalName() + "." + e.name());
		} else if (o.getClass().isArray()) {
			Class<?> type = o.getClass().getComponentType();
			int arrlength = Array.getLength(o);

			String array = "";
			for (int i = 0; i < arrlength; ++i) {
				array += (type.equals(String.class) ? "\"" : type.equals(char.class) ? "\'" : "")
						+ Array.get(o, i)
						+ (type.equals(String.class) ? "\"" : type.equals(char.class) ? "\'" : "");

				array += (type.equals(float.class) ? "f" : type.equals(double.class) ? "d"
						: type.equals(Class.class) ? ".class" : "");

				if (i + 1 < arrlength)
					array += ", ";
			}
			if (type.equals(Class.class))
				array = array.replaceAll("class ", "");

			result.append("new " + type.getCanonicalName() + (type.equals(Class.class) ? "<?>" : "")
										+ "[]{" + array + "}");
		} else if (o instanceof Annotation) {
			Annotation a = (Annotation) o;
			if (!generatedClass.containsKey(a.annotationType())) {
				generateAnnotationFactory(a.annotationType(), new StringBuilder());
			}
			result.append("new ExtensionRegistry_Impl." + a.annotationType().getSimpleName()
					+ "AnnotationFactory_Impl().build()");
		}
		return result.toString();
	}
}
